#include <gtest/gtest.h>
#include <gflags/gflags.h>
#include <thread>
#include <chrono>

#include "mysql_outbox.hpp"
#include "outbox_sync_worker.hpp"
#include "utils.hpp"

DEFINE_string(sync_mysql_user, "root", "Sync 测试 MySQL 用户名");
DEFINE_string(sync_mysql_password, "ok", "Sync 测试 MySQL 密码");
DEFINE_string(sync_mysql_db, "suye", "Sync 测试 MySQL 数据库名称");
DEFINE_string(sync_mysql_host, "127.0.0.1", "Sync 测试 MySQL 主机地址");
DEFINE_string(sync_mysql_charset, "utf8", "Sync 测试 MySQL 字符集");
DEFINE_int32(sync_mysql_port, 0, "Sync 测试 MySQL 端口，为 0 时使用默认端口");
DEFINE_int32(sync_mysql_pool_size, 1, "Sync 测试连接池大小");

namespace
{
class SyncWorkerTest : public ::testing::Test
{
protected:
    static void SetUpTestSuite()
    {
        _db = suye::ODBFactory::create(FLAGS_sync_mysql_user,
                                       FLAGS_sync_mysql_password,
                                       FLAGS_sync_mysql_db,
                                       FLAGS_sync_mysql_host,
                                       FLAGS_sync_mysql_charset,
                                       FLAGS_sync_mysql_port,
                                       FLAGS_sync_mysql_pool_size);
        ASSERT_TRUE(_db);
        _outbox_table = std::make_shared<suye::OutboxTable>(_db);
        ASSERT_TRUE(_outbox_table->truncate());
    }

    static void TearDownTestSuite()
    {
        if (_outbox_table)
        {
            _outbox_table->truncate();
        }
        _outbox_table.reset();
        _db.reset();
    }

    void SetUp() override
    {
        ASSERT_TRUE(_outbox_table->truncate());
        _processed_events.clear();
    }

    static std::shared_ptr<odb::core::database> _db;
    static std::shared_ptr<suye::OutboxTable> _outbox_table;
    static std::vector<std::string> _processed_events;
};

std::shared_ptr<odb::core::database> SyncWorkerTest::_db;
std::shared_ptr<suye::OutboxTable> SyncWorkerTest::_outbox_table;
std::vector<std::string> SyncWorkerTest::_processed_events;
} // namespace

TEST_F(SyncWorkerTest, ProcessEventSuccess)
{
    const std::string event_id = suye::uuid();
    auto event = std::make_shared<suye::OutboxEvent>(
        event_id,
        "user",
        "uid-123",
        "TEST_EVENT",
        "{\"data\":\"test\"}");

    {
        odb::transaction trans(SyncWorkerTest::_db->begin());
        ASSERT_TRUE(SyncWorkerTest::_outbox_table->record_event(event));
        trans.commit();
    }

    suye::OutboxSyncWorker worker(SyncWorkerTest::_outbox_table, 10, std::chrono::milliseconds(100));
    
    worker.register_handler("TEST_EVENT", [](const std::shared_ptr<suye::OutboxEvent>& evt, std::string& err) {
        SyncWorkerTest::_processed_events.push_back(evt->event_id());
        return true;
    });

    worker.run_once();

    ASSERT_EQ(SyncWorkerTest::_processed_events.size(), 1u);
    EXPECT_EQ(SyncWorkerTest::_processed_events[0], event_id);

    auto stored = SyncWorkerTest::_outbox_table->find_by_event_id(event_id);
    ASSERT_TRUE(stored);
    EXPECT_EQ(stored->status(), suye::OutboxStatus::Completed);
}

TEST_F(SyncWorkerTest, ProcessEventFailureWithRetry)
{
    const std::string event_id = suye::uuid();
    auto event = std::make_shared<suye::OutboxEvent>(
        event_id,
        "user",
        "uid-fail",
        "FAIL_EVENT",
        "{\"data\":\"fail\"}",
        suye::OutboxStatus::Pending,
        2);

    {
        odb::transaction trans(SyncWorkerTest::_db->begin());
        ASSERT_TRUE(SyncWorkerTest::_outbox_table->record_event(event));
        trans.commit();
    }

    suye::OutboxSyncWorker worker(SyncWorkerTest::_outbox_table, 10, std::chrono::milliseconds(100));
    
    int attempt_count = 0;
    worker.register_handler("FAIL_EVENT", [&attempt_count](const std::shared_ptr<suye::OutboxEvent>& evt, std::string& err) {
        ++attempt_count;
        err = "simulated failure";
        return false;
    });

    worker.run_once();
    EXPECT_EQ(attempt_count, 1);

    auto stored = SyncWorkerTest::_outbox_table->find_by_event_id(event_id);
    ASSERT_TRUE(stored);
    EXPECT_EQ(stored->status(), suye::OutboxStatus::Pending);
    EXPECT_EQ(stored->retry_count(), 1u);

    worker.run_once();
    EXPECT_EQ(attempt_count, 2);

    stored = SyncWorkerTest::_outbox_table->find_by_event_id(event_id);
    ASSERT_TRUE(stored);
    EXPECT_EQ(stored->status(), suye::OutboxStatus::Failed);
    EXPECT_EQ(stored->retry_count(), 2u);

    worker.run_once();
    EXPECT_EQ(attempt_count, 2);
}

TEST_F(SyncWorkerTest, BackgroundWorkerThread)
{
    const std::string event_id = suye::uuid();
    auto event = std::make_shared<suye::OutboxEvent>(
        event_id,
        "user",
        "uid-background",
        "BG_EVENT",
        "{\"data\":\"background\"}");

    {
        odb::transaction trans(SyncWorkerTest::_db->begin());
        ASSERT_TRUE(SyncWorkerTest::_outbox_table->record_event(event));
        trans.commit();
    }

    suye::OutboxSyncWorker worker(SyncWorkerTest::_outbox_table, 10, std::chrono::milliseconds(200));
    
    worker.register_handler("BG_EVENT", [](const std::shared_ptr<suye::OutboxEvent>& evt, std::string& err) {
        SyncWorkerTest::_processed_events.push_back(evt->event_id());
        return true;
    });

    worker.start();
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    worker.stop();

    ASSERT_EQ(SyncWorkerTest::_processed_events.size(), 1u);
    EXPECT_EQ(SyncWorkerTest::_processed_events[0], event_id);

    auto stored = SyncWorkerTest::_outbox_table->find_by_event_id(event_id);
    ASSERT_TRUE(stored);
    EXPECT_EQ(stored->status(), suye::OutboxStatus::Completed);
}

int main(int argc, char* argv[])
{
    google::ParseCommandLineFlags(&argc, &argv, true);
    suye::Init_logger(false, "", 0);
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}


